import java.util.LinkedList;
import java.util.Random;
import java.util.Scanner;
import java.io.BufferedReader;
import java.io.FileReader;

import java.util.StringTokenizer;

import java.awt.event.*;
/**
 * Represents the map and the logic of the game
 * 
 * @author Jofry HS
 * @version 16-07-2010
 */
public class Maze implements ActionListener
{   
    // Some constants for game state/condition
    final public static int CONDITION_NOTHING = 0;
    final public static int CONDITION_WIN = 1;
    final public static int CONDITION_LOSE = 2;
    
    // List of all the bad bots trying to ruin the day
    private LinkedList<Robot> badBots = new LinkedList<Robot>();
    
    // Good bot searching for his home
    private Robot goodBot;
 
    private int width, height;              // Width and height of the map
    private Tile [][] map;                  // The map of the maze
    private int goalX, goalY;               // Home location
    
    private boolean moveAllowed = true;     // Flag that allow/restrict movement
    private boolean gameEnded = false;      // Game ended?
    private int condition = CONDITION_NOTHING;  // Game state
    
    private String fileName;    // In case you need to reset and read the same file
    
    private MazeGUI gui;        // The GUI
    
    /**
     * Default constructor, asking the user for the map data
     */
    public Maze()
    {
       System.out.println("Enter the map file: ");  // Get the map name from the user
       Scanner in = new Scanner(System.in);
       fileName = in.nextLine();
       
       readFile(fileName);                          // Read file
       gui = new MazeGUI(this);
    }
    
    /**
     * Read the map data given the file name
     */
    public void readFile(String fileName)
    {
        // Reset variables as much as possible
        condition = CONDITION_NOTHING;
        gameEnded = false;
        moveAllowed = true;
        
         try {
            /**
             * Structure of map file:
             * Description of the field     -   Example
             * --------------------------------------------------
             * MAP WIDTH                    -   20
             * MAP HEIGHT                   -   12
             * STARTING LOCATION X          -   0
             * STARTING LOCATION Y          -   0
             * GOAL LOCATION X              -   19
             * GOAL LOCATION Y              -   0
             * NUMBER OF BAD BOTS           -   2
             * LOCATION OF THE BAD BOTS     -   4,7,10,7
             * NUMBER OF OBSTACLES          -   13
             * LOCATION OF THE OBSTACLES    -   2,3,3,3,4,....   
             * --------------------------------------------------
             * 
             * Location of bad bots and obstacles are separated by comma, with repeating sequence of x and y position
             * Therefore following above examples, if there are 13 NUMBER OF OBSTACLES, we expect 26 numbers separated
             * by comma in LOCATION OF THE OBSTACLES             * 
             */
            
            BufferedReader reader = new BufferedReader(new FileReader("data/" + fileName)); // Read from supplied file name
            
            // Remember to convert it back to Integer value
            width = Integer.parseInt(reader.readLine());
            height = Integer.parseInt(reader.readLine());
            
            // Initialize the map
            map = new Tile[width][height];            
            for (int row = 0; row < width; row++)
            {
                for (int col = 0; col < height; col++)
                {
                    map[row][col] = new Tile();
                }
            }
            
            // Read starting location
            int goodBotX = Integer.parseInt(reader.readLine());
            int goodBotY = Integer.parseInt(reader.readLine());
            goodBot = new Robot(goodBotX, goodBotY);
            
            // Read goal location
            goalX = Integer.parseInt(reader.readLine());
            goalY = Integer.parseInt(reader.readLine());
            
            // Read all bad bots using tokenizer
            int numBadBots = Integer.parseInt(reader.readLine());
            String badBotLocation = reader.readLine();
            StringTokenizer tokenizer = new StringTokenizer(badBotLocation,",");            
            for(int i = 0; i < numBadBots; i++)
            {
                int botX = Integer.parseInt(tokenizer.nextToken());
                int botY = Integer.parseInt(tokenizer.nextToken());
                Robot badBot = new Robot(botX,botY);
                badBots.add(badBot);
            }            
            
            // Read all obstacles
            int numObstacles = Integer.parseInt(reader.readLine());
            String obstacleLocation = reader.readLine();
            tokenizer = new StringTokenizer(obstacleLocation,",");            
            for(int i = 0; i < numObstacles; i++)
            {
                int obstacleX = Integer.parseInt(tokenizer.nextToken());
                int obstacleY = Integer.parseInt(tokenizer.nextToken());
                map[obstacleX][obstacleY].setPassable(false);
            }  
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
    
    /**
     * Undo all robots action on the map if possible
     */
    public void undo()
    {
        if (goodBot.canUndo())
        {
            goodBot.undo(map);
            for (int i = 0; i < badBots.size(); i++)
            {   
                Robot bad = badBots.get(i);
                bad.undo(map);
            }
        }
        gui.updateGraphics();
    }
    
    /**
     * One of these is fired from the Button pressed
     */
    public void actionPerformed(ActionEvent e)
    {
        String command = e.getActionCommand();
        
        // Reset
        if (command.equals("Reset"))
        {
            badBots.clear();
            readFile(fileName);
            gui.updateGraphics();
        }      
        
        // Most buttons yield no effect once the game ended
        if (gameEnded) return;
        
        // Undo
        if (command.equals("Undo"))
        {
            undo();
        }
        
        boolean moved = false;  // Keep track whether good bot initiated a move
        // If move action is allowed (e.g. not waiting for bad bots to move)
        if (moveAllowed)
        {
            // Moves according to directional button
            if (command.equals("/\\"))
            {
                if (isEmptyTile(goodBot.getX(), goodBot.getY() - 1))    // Move only if next spot is empty
                {
                    goodBot.saveLocation(); // Save location in case of undo
                    goodBot.moveUp(map);    // Move
                    moved = true;           // Yes, we just moved
                }
            }
            else if (command.equals(">"))
            {
                if (isEmptyTile(goodBot.getX()+1, goodBot.getY()))
                {
                    goodBot.saveLocation();
                    goodBot.moveRight(map);
                    moved = true;
                }
            }
            else if (command.equals("\\/"))
            {
                if (isEmptyTile(goodBot.getX(), goodBot.getY()+1))
                {
                    goodBot.saveLocation();
                    goodBot.moveDown(map);
                    moved = true;
                }
            }
            else if (command.equals("<"))
            {
                if (isEmptyTile(goodBot.getX()-1, goodBot.getY()))
                {
                    goodBot.saveLocation();
                    goodBot.moveLeft(map);
                    moved = true;
                }
            }
            else if (command.equals("Stay"))
            {
                goodBot.saveLocation();
                moved = true;
            }
            
            // Check for win condition
            if (goodBot.getX() == goalX && goodBot.getY() == goalY)
            {
                condition = CONDITION_WIN;
                endGame();
            }
            
            // If good bot initiated the move, time for bad bots to move
            if (moved)
            {
                gui.updateGraphics();                
                // Important bit of the method, remember we need to escape from EDT to prevent freeze
                // Creating a new thread ensure we can actually see the animation
                Thread t = new Thread(new Runnable() { 
                    public void run() {
                        moveBadBots();
                    }            
                });
                t.start();
            }
        }
    }
    
    /**
     * Validating the specified spot
     */
    public boolean isEmptyTile(int x, int y)
    {
        if (x < 0 || x >= width || y < 0 || y >= height)
            return false;
        if (map[x][y].isFree())
            return true;
        return false;
    }
    
    /**
     * Command all bad bots to hunt the good bot one (actually two) steps at a time
     */
    public void moveBadBots()
    {
        moveAllowed = false;    // Prevent user to move while bad bots are still moving
        if (gameEnded) return;  // No point of moving bad bots if good bot is already at home
        
        // Set the counter to twice, apparently bad bots move twice as fast as the good one
        for (int moveCounter = 0; moveCounter < 2; moveCounter++)
        {
            for (int i = 0; i < badBots.size(); i++)
            {
                Robot bad = badBots.get(i);
                boolean moved = false;
                
                if (moveCounter == 0)
                    bad.saveLocation();     // Save location before it moves in case of undo
                
                // Following blocks of code describe how bad bots are moving towards the hero
                if (bad.getX() > goodBot.getX())
                {
                    if((bad.getX() - 1) == goodBot.getX() && bad.getY() == goodBot.getY())
                    {
                        condition = CONDITION_LOSE;
                    }
                    if(isEmptyTile(bad.getX()-1,bad.getY()))
                    {
                        bad.moveLeft(map);
                        moved = true;
                    }
                }
                else if (bad.getX() < goodBot.getX())
                {
                    if((bad.getX() + 1) == goodBot.getX() && bad.getY() == goodBot.getY())
                    {
                        condition = CONDITION_LOSE;
                    }
                    if(isEmptyTile(bad.getX()+1,bad.getY()))
                    {
                        bad.moveRight(map);
                        moved = true;
                    }
                }
                
                if (!moved)
                {
                    if(bad.getY() > goodBot.getY())
                    {
                        if(bad.getX()== goodBot.getX() && (bad.getY()-1) == goodBot.getY())
                        {
                            condition = CONDITION_LOSE;
                        }
                         if(isEmptyTile(bad.getX(),bad.getY()-1))
                         {
                             bad.moveUp(map);
                             moved = true;
                         }
                    }
                    else if(bad.getY() < goodBot.getY())
                    {
                        if(bad.getX()== goodBot.getX() && (bad.getY()+1) == goodBot.getY())
                        {
                            condition = CONDITION_LOSE;
                        }
                         if(isEmptyTile(bad.getX(),bad.getY()+1))
                         {
                             bad.moveDown(map);
                             moved = true;
                         }
                    }
                }  
                
                if (condition == CONDITION_LOSE)
                {
                    endGame();
                }
            }
            
            try {
                Thread.sleep(100);  // Put some delay to show animation
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
            gui.updateGraphics();                
        }        
        moveAllowed = true;
    }
    
    /**
     * Signalling the end of the game
     */
    public void endGame()
    {
        if (condition == CONDITION_LOSE)
        {
            System.out.println("Game over! You lose!");
            gui.setStatus("Uh-oh! Try again!");
            gameEnded = true;
        }
        else if (condition == CONDITION_WIN)
        {
            System.out.println("You win!");            
            gui.setStatus("You cleared the stage!");
            gameEnded = true;
        }
        gui.updateGraphics();
    }
    
    // Getters and setters
    public Tile [][] getMap()
    {
        return map;
    }
    
    public LinkedList<Robot> getBadBots()
    {
        return badBots;
    }
    
    public Robot getGoodBot()
    {
        return goodBot;
    }
    
    public int getGoalX()
    {
        return goalX;
    }
    
    public int getGoalY()
    {
        return goalY;
    }
    
    public int getWidth()
    {
        return width;
    }

    public int getHeight()
    {
        return height;
    }
    
    public int getCondition()
    {
        return condition;
    }
    
    // To start the game
    public static void main(String [] args)
    {       
       Maze maze = new Maze();
    }
}
